package org.example.guavalearn.proxy;

import com.google.common.reflect.TypeToken;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;

class RepositoryInvocationHandler implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(RepositoryInvocationHandler.class);

    private static final Cache cache = new Cache();
    private static final String TYPE_TOKEN = "typeToken";

    private record ItemKey(Object proxy, Method method, String itemType) {
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        String methodName = method.getName();
        Proxy proxyObj = (Proxy) proxy;
        if (!(methodName.equals("toString") || methodName.equals("hashCode"))) {
            log.info("invoke {} on {}", method, proxyObj);
            inspectType(proxy);
        }
        return switch (methodName) {
            case "findAll" -> findAll(proxy, method);
            case "toString" -> ObjectUtil.toString(proxy);
            case "equals" -> false;
            case "hashCode" -> ObjectUtil.hashCode(proxy);
            default -> null;
        };
    }

    private List<?> findAll(@NonNull Object proxy, @NonNull Method method) {
        Class<?> T = getArgumentType0(proxy, method);
        return new ArrayList<>();
    }

    private Class<?> getArgumentType0(Object proxy, Method method) {
        Class<?> T = null;
        TypeToken<?> type = getTypeToken(proxy, method);
        if (type != null) {
            log.info("type token: {}", ObjectUtil.toString(type));
            ParameterizedType type1 = (ParameterizedType) type.getType();
            Type[] typeArguments = type1.getActualTypeArguments();
            T = (Class<?>) typeArguments[0];
        }
        log.info("T: {}", T);
        return T;
    }

    private TypeToken<?> getTypeToken(Object proxy, Method method) {
        ItemKey key = new ItemKey(proxy, method, TYPE_TOKEN);
        return cache.get(key, () -> {
            TypeToken<?> proxyType = TypeToken.of(proxy.getClass());
            Class<?> methodDeclaringClass = method.getDeclaringClass();
            TypeToken<?>.TypeSet types = proxyType.getTypes();
            for (TypeToken<?> type : types) {
                if (type.getRawType() == methodDeclaringClass) {
                    return type;
                }
            }
            return null;
        });
    }

    private void inspectType(@NonNull Object proxy) {
        TypeToken<?> t = TypeToken.of(proxy.getClass());
        log.info("type: {}", t.getType());
        log.info("raw type: {}", t.getRawType());

        log.info("---- types of {}: ----", t);
        TypeToken<?>.TypeSet types = t.getTypes();
        for (TypeToken<?> type : types) {
            log.info("  - {}", type);
        }
    }
}
